Plugin Cấu trúc dành cho Jira rất hữu ích cho công việc hàng ngày với các nhiệm vụ và phân tích chúng; nó đưa khả năng trực quan hóa và cấu trúc của vé Jira lên một tầm cao mới và thực hiện tất cả điều này ngay lập tức.
Và không phải ai cũng biết điều đó, nhưng chức năng của các công thức Cấu trúc có thể khiến bạn kinh ngạc. Bằng cách sử dụng các công thức, bạn có thể tạo các bảng cực kỳ hữu ích có thể đơn giản hóa đáng kể công việc với các tác vụ và quan trọng nhất là chúng rất hữu ích để thực hiện phân tích sâu hơn về các bản phát hành, sử thi và dự án.
Trong bài viết này, bạn sẽ biết cách tạo công thức của riêng mình, bắt đầu từ những ví dụ đơn giản nhất và kết thúc bằng những trường hợp phức tạp nhưng khá hữu ích.
Vậy văn bản này dành cho ai? Người ta có thể thắc mắc, tại sao lại viết một bài báo khi tài liệu chính thức trên trang web ALM Works vẫn ở ngay đó chờ người đọc tìm hiểu. Điều đó đúng. Tuy nhiên, tôi là một trong những người thậm chí còn không hề có chút khái niệm nào rằng Cấu trúc đang che giấu chức năng rộng rãi như vậy: “Đợi đã, đây vốn là một lựa chọn sao?!” Nhận thức đó khiến tôi suy nghĩ, có thể có những người khác vẫn chưa biết những việc họ có thể làm với công thức và Cấu trúc.
Bài viết này cũng sẽ hữu ích cho những người đã quen thuộc với các công thức. Bạn sẽ tìm hiểu một số tùy chọn thực tế thú vị để sử dụng các trường tùy chỉnh và có thể mượn một số tùy chọn trong số đó cho các dự án của bạn. Nhân tiện, nếu bạn có bất kỳ ví dụ thú vị nào của riêng mình, tôi sẽ rất vui nếu bạn chia sẻ chúng trong phần nhận xét .
Mỗi ví dụ đều được phân tích chi tiết, từ mô tả vấn đề đến giải thích về mã, đủ kỹ lưỡng để không còn câu hỏi nào. Tất nhiên, bên cạnh phần giải thích, mỗi ví dụ đều được minh họa bằng mã mà bạn có thể tự mình thử mà không cần đi sâu vào phân tích.
Nếu bạn không thích đọc nhưng lại quan tâm đến các công thức, hãy xem hội thảo trực tuyến của ALM Works . Những điều này giải thích những điều cơ bản trong 40 phút; thông tin được trình bày ở đó một cách rất nén.
Bạn không cần bất kỳ kiến thức bổ sung nào để hiểu các ví dụ, vì vậy bất kỳ ai đã làm việc với Jira và Structure đều có thể lặp lại các ví dụ trong bảng của họ mà không gặp vấn đề gì.
Các nhà phát triển đã cung cấp cú pháp khá linh hoạt bằng ngôn ngữ Expr của họ. Về cơ bản, triết lý ở đây là “viết như bạn muốn và nó sẽ thành công”.
Vậy hãy bắt đầu!
Vậy tại sao chúng ta lại muốn sử dụng công thức? Chà, đôi khi hóa ra là chúng ta không có đủ các trường Jira tiêu chuẩn, chẳng hạn như “Người được giao”, “Điểm câu chuyện”, v.v. Hoặc chúng ta cần tính toán một số lượng cho một số trường nhất định, hiển thị dung lượng còn lại theo phiên bản và tìm hiểu xem tác vụ đã thay đổi trạng thái bao nhiêu lần. Có thể chúng tôi thậm chí còn muốn hợp nhất nhiều trường thành một để làm cho Cấu trúc của chúng tôi dễ đọc hơn.
Để giải quyết những vấn đề này, chúng ta cần các công thức và chúng ta sẽ sử dụng những công thức đó để tạo các trường tùy chỉnh.
Điều đầu tiên chúng ta cần làm là hiểu cách thức hoạt động của một công thức. Nó cho phép chúng ta áp dụng một số loại hoạt động cho một chuỗi. Vì chúng tôi đang tải nhiều tác vụ lên cấu trúc nên công thức sẽ được áp dụng cho từng dòng của toàn bộ bảng. Thông thường, mọi hoạt động của nó đều nhằm mục đích làm việc với các nhiệm vụ trong các dòng này.
Vì vậy, nếu chúng ta yêu cầu công thức hiển thị một số trường Jira, ví dụ: “Người được giao”, thì công thức sẽ được áp dụng cho từng nhiệm vụ và chúng ta sẽ có một cột “Người được giao” khác.
Công thức bao gồm một số thực thể cơ bản:
Chúng ta sẽ trở nên quen thuộc hơn với các công thức và cú pháp của chúng thông qua một số ví dụ và chúng ta sẽ đi qua sáu trường hợp thực tế.
Trước khi xem từng ví dụ, chúng tôi sẽ chỉ ra những tính năng Cấu trúc nào chúng tôi đang sử dụng; các tính năng mới chưa được giải thích sẽ được in đậm. Mỗi ví dụ sau đây sẽ có mức độ phức tạp ngày càng tăng. Chúng được sắp xếp nhằm dần dần giới thiệu cho bạn những tính năng quan trọng của công thức.
Đây là cấu trúc cơ bản bạn sẽ thấy mỗi lần:
Những ví dụ này bao gồm các chủ đề từ ánh xạ biến đổi đến các mảng phức tạp:
Trước tiên, hãy tìm hiểu cách tạo các trường tùy chỉnh bằng công thức. Ở phần trên bên phải của Cấu trúc, ở cuối tất cả các cột, có biểu tượng “+” - hãy nhấp vào biểu tượng đó. Trong trường xuất hiện, hãy viết “Công thức…” và chọn mục thích hợp.
Hãy thảo luận về việc lưu một công thức. Thật không may, vẫn không thể lưu riêng một công thức cụ thể ở đâu đó (chỉ trong sổ ghi chép của bạn, như tôi đã làm). Tại hội thảo trực tuyến ALM Works, nhóm đã đề cập rằng họ đang làm việc trên một ngân hàng công thức nhưng hiện tại, cách duy nhất để lưu chúng là lưu toàn bộ chế độ xem cùng với công thức.
Khi hoàn tất công thức, chúng ta cần nhấp vào chế độ xem cấu trúc của mình (rất có thể nó sẽ được đánh dấu bằng dấu hoa thị màu xanh lam) và nhấp vào “Lưu” để ghi đè chế độ xem hiện tại. Hoặc bạn có thể nhấp vào “Save As…” để tạo chế độ xem mới. (Đừng quên cung cấp nó cho những người dùng Jira khác vì chế độ xem mới được đặt ở chế độ riêng tư theo mặc định.)
Công thức sẽ được lưu vào các trường còn lại trong một chế độ xem cụ thể và bạn có thể xem công thức đó trong tab “Nâng cao” của menu “Xem chi tiết”.
Bắt đầu từ phiên bản 8.2, Structure hiện có khả năng lưu công thức chỉ bằng 3 cú nhấp chuột nhanh chóng.
Hộp thoại lưu có sẵn từ cửa sổ chỉnh sửa công thức. Nếu cửa sổ này không mở, chỉ cần nhấp vào biểu tượng tam giác ▼ ở cột mong muốn.
Trong cửa sổ chỉnh sửa, chúng ta thấy trường “Cột đã lưu”, ở bên phải có biểu tượng có thông báo màu xanh lam, có nghĩa là những thay đổi trong công thức chưa được lưu. Nhấp vào biểu tượng này và chọn tùy chọn “Save as…”.
Sau đó nhập tên cho cột (công thức) của chúng tôi và chọn khoảng trống để lưu nó. “Cột của tôi” nếu chúng ta muốn lưu nó vào danh sách cá nhân. “Toàn cầu”, để công thức sẽ được lưu trong danh sách chung, nơi tất cả người dùng trong Cấu trúc của bạn có thể chỉnh sửa nó. Nhấp vào để lưu".
Bây giờ công thức của chúng tôi đã được lưu. Chúng ta có thể tải nó ở bất kỳ cấu trúc nào hoặc lưu lại nó từ bất cứ đâu. Bằng cách lưu lại công thức, nó sẽ được cập nhật trong tất cả các cấu trúc mà nó được sử dụng.
Ánh xạ biến cũng được lưu cùng với công thức, nhưng chúng ta sẽ nói về ánh xạ sau.
Bây giờ, hãy chuyển sang các ví dụ của chúng tôi!
Chúng ta cần một bảng có danh sách các nhiệm vụ cũng như ngày bắt đầu và ngày kết thúc để thực hiện các nhiệm vụ đó. Chúng tôi cũng cần bảng để xuất nó sang một excel-gantt riêng. Thật không may, Jira và Structure không biết cách cung cấp những ngày tháng như vậy ngay lập tức.
Ngày bắt đầu và ngày kết thúc là ngày chuyển đổi sang các trạng thái cụ thể, trong trường hợp của chúng tôi, đây là “Đang tiến hành” và “Đã đóng”. Chúng tôi cần lấy những ngày này và hiển thị từng ngày trong một trường riêng biệt (điều này là cần thiết để xuất thêm sang gantt). Vì vậy, chúng ta sẽ có hai trường (hai công thức).
Các tính năng cấu trúc được sử dụng
Một ví dụ mã
Trường cho ngày bắt đầu:
firstTransitionToStart
Trường cho ngày kết thúc:
latestTransitionToDone
Trong trường hợp này, mã là một biến duy nhất, firstTransitionToStart, cho trường ngày bắt đầu và mới nhấtTransitionToDone cho trường thứ hai.
Bây giờ hãy tập trung vào trường ngày bắt đầu. Mục tiêu của chúng tôi là lấy ngày nhiệm vụ được chuyển sang trạng thái “Đang tiến hành” (điều này tương ứng với thời điểm bắt đầu hợp lý của nhiệm vụ), do đó, biến được đặt tên, khá rõ ràng để tránh việc phải đoán sau này, như “chuyển đổi đầu tiên sang bắt đầu".
Để biến một ngày thành một biến, chúng ta chuyển sang ánh xạ biến. Hãy lưu công thức của chúng tôi bằng cách nhấp vào nút “Save”.
Biến của chúng tôi xuất hiện trong phần “Biến”, có dấu chấm than bên cạnh. Cấu trúc chỉ ra rằng nó không thể liên kết một biến với một trường trong Jira và chúng ta sẽ phải tự làm điều đó (tức là ánh xạ nó).
Bấm vào biến và chuyển đến giao diện bản đồ. Chọn trường hoặc thao tác cần thiết - tìm thao tác “Ngày chuyển đổi…”. Để làm điều đó, hãy viết “chuyển tiếp” vào trường lựa chọn. Bạn sẽ được cung cấp một số tùy chọn cùng một lúc và một trong số chúng phù hợp với chúng tôi: “Chuyển đổi lần đầu sang Đang tiến hành”. Nhưng để minh họa cách hoạt động của ánh xạ, hãy chọn tùy chọn “Ngày chuyển đổi…”.
Sau đó, bạn cần chọn trạng thái diễn ra quá trình chuyển đổi và thứ tự của quá trình chuyển đổi này - lần đầu tiên hoặc lần cuối cùng.
Chọn hoặc nhập vào “Trạng thái” — “Trạng thái: Đang tiến hành” (hoặc trạng thái tương ứng trong Quy trình làm việc của bạn) và trong “Chuyển đổi” — “Chuyển đổi đầu tiên sang trạng thái”, vì việc bắt đầu thực hiện một nhiệm vụ là lần chuyển đổi đầu tiên sang trạng thái tương ứng.
Nếu thay vì “Ngày chuyển đổi…”, chúng tôi chọn tùy chọn được đề xuất ban đầu “Chuyển đổi đầu tiên sang đang tiến hành”, thì kết quả sẽ gần như giống nhau - Cấu trúc sẽ chọn các tham số cần thiết cho chúng tôi. Điều duy nhất là, thay vì “Trạng thái: Đang tiến hành”, chúng ta sẽ có “Danh mục: Đang tiến hành”.
Hãy để tôi lưu ý một đặc điểm quan trọng: trạng thái và danh mục là hai thứ khác nhau. Trạng thái là một trạng thái cụ thể, rõ ràng nhưng một danh mục có thể bao gồm một số trạng thái. Chỉ có ba loại: “Việc cần làm”, “Đang tiến hành” và “Hoàn thành”. Trong Jira, chúng thường được đánh dấu lần lượt bằng các màu xám, xanh lam và xanh lục. Trạng thái phải thuộc về một trong các loại này.
Tôi khuyên bạn nên chỉ ra một trạng thái cụ thể trong những trường hợp như thế này để tránh nhầm lẫn với các trạng thái cùng danh mục. Ví dụ: chúng tôi có hai trạng thái của danh mục “Việc cần làm” trên dự án là “Mở” và “Hàng đợi QA”.
Hãy quay lại ví dụ của chúng tôi.
Khi đã chọn các tùy chọn cần thiết, chúng ta có thể nhấp vào “< Quay lại danh sách biến” để hoàn thành các tùy chọn ánh xạ cho biến FirstTransitionToStart. Nếu chúng ta làm đúng mọi thứ, chúng ta sẽ thấy dấu kiểm màu xanh lục.
Đồng thời, trong trường tùy chỉnh của chúng tôi, chúng tôi thấy một số con số lạ trông không giống ngày tháng chút nào. Trong trường hợp của chúng tôi, kết quả của công thức sẽ là giá trị của biến FirstTransitionToStart và giá trị của nó là mili giây kể từ tháng 1 năm 1970. Để có được ngày chính xác, chúng ta cần chọn một định dạng hiển thị công thức cụ thể.
Lựa chọn định dạng nằm ở cuối cửa sổ chỉnh sửa. Theo mặc định, General General được chọn ở đó. Chúng ta cần “Ngày/Giờ” để hiển thị ngày chính xác.
Đối với trường thứ hai, mới nhấtTransitionToDone, chúng tôi sẽ thực hiện tương tự. Sự khác biệt duy nhất là khi ánh xạ, chúng ta có thể chọn danh mục “Hoàn thành” chứ không phải trạng thái (vì thường chỉ có một trạng thái hoàn thành nhiệm vụ rõ ràng). Chúng tôi chọn “Chuyển đổi mới nhất” làm tham số chuyển đổi vì chúng tôi quan tâm đến quá trình chuyển đổi gần đây nhất sang danh mục “Hoàn thành”.
Kết quả cuối cùng cho 2 trường sẽ như thế này.
Bây giờ hãy xem cách đạt được kết quả tương tự nhưng với định dạng hiển thị của riêng chúng ta.
Chúng tôi không hài lòng với định dạng hiển thị ngày từ ví dụ trước vì chúng tôi cần một định dạng đặc biệt cho bảng gantt — “01.01.2022”.
Hãy hiển thị ngày tháng bằng các hàm có sẵn trong Cấu trúc, chỉ định định dạng phù hợp với chúng ta.
Đặc điểm cấu trúc được sử dụng
Một ví dụ mã
FORMAT_DATETIME(firstTransitionToStart;"dd.MM.yyyy")
Các nhà phát triển đã cung cấp nhiều chức năng khác nhau, bao gồm một chức năng riêng biệt để hiển thị ngày theo định dạng riêng của chúng tôi: FORMAT_DATETIME; đó là những gì chúng ta sẽ sử dụng. Hàm sử dụng hai đối số: một ngày và một chuỗi có định dạng mong muốn.
Chúng ta thiết lập biến firstTransitionToStart (đối số đầu tiên) bằng cách sử dụng các quy tắc ánh xạ giống như trong ví dụ trước. Đối số thứ hai là một chuỗi chỉ định định dạng và chúng tôi xác định nó như sau: “dd.MM.yyyy”. Điều này tương ứng với biểu mẫu mà chúng tôi muốn, “01.01.2022”.
Như vậy, công thức của chúng ta sẽ ngay lập tức cho kết quả ở dạng mong muốn. Vì vậy, chúng ta có thể giữ tùy chọn “Chung” trong cài đặt trường.
Trường thứ hai có ngày kết thúc công việc được thực hiện theo cách tương tự. Kết quả là cấu trúc sẽ giống như trong hình bên dưới.
Về nguyên tắc, không có khó khăn gì đáng kể khi làm việc với cú pháp công thức. Nếu bạn cần một biến, hãy viết tên của nó; nếu bạn cần một hàm, một lần nữa, chỉ cần viết tên của nó và truyền các đối số (nếu chúng được yêu cầu).
Khi Structure gặp một tên không xác định, nó sẽ coi đó là một biến và cố gắng ánh xạ chính tên đó hoặc yêu cầu chúng tôi trợ giúp.
Nhân tiện, một lưu ý quan trọng: Cấu trúc không phân biệt chữ hoa chữ thường, vì vậy firstTransitionToStart, firsttransitiontostart và FirSttrAnsItiontOStarT là cùng một biến. Quy tắc tương tự áp dụng cho các chức năng. Để đạt được kiểu mã rõ ràng, trong các ví dụ, chúng tôi sẽ cố gắng tuân thủ các quy tắc về Công ước viết hoa của MSDN.
Bây giờ hãy đi sâu vào cú pháp và xem xét một định dạng đặc biệt để hiển thị kết quả.
Chúng tôi làm việc với các tác vụ thông thường (Tác vụ, Lỗi, v.v.) và với các tác vụ kiểu Câu chuyện có nhiệm vụ phụ. Tại một thời điểm nào đó, chúng ta cần tìm hiểu những nhiệm vụ và nhiệm vụ phụ mà nhân viên đã thực hiện trong một khoảng thời gian nhất định.
Vấn đề là nhiều nhiệm vụ phụ không cung cấp thông tin về chính câu chuyện, vì chúng được gọi là “làm việc trên câu chuyện”, “thiết lập” hoặc chẳng hạn như “kích hoạt hiệu ứng”. Và nếu chúng ta yêu cầu danh sách nhiệm vụ trong một khoảng thời gian nhất định, chúng ta sẽ nhận được hàng tá nhiệm vụ với tên “làm việc trên câu chuyện” mà không có bất kỳ thông tin hữu ích nào khác.
Chúng tôi muốn có một chế độ xem với danh sách được chia thành hai cột: nhiệm vụ và nhiệm vụ chính, để trong tương lai có thể nhóm danh sách như vậy theo nhân viên.
Trong dự án của chúng tôi, chúng tôi có hai tùy chọn khi một tác vụ có thể có cha mẹ:
Vì vậy, chúng ta phải:
Để đơn giản hóa việc nhận thức thông tin, chúng tôi sẽ tô màu văn bản của loại nhiệm vụ: nghĩa là “[Story]” hoặc “[Epic]”.
Những gì chúng tôi sẽ sử dụng:
Một ví dụ mã
if( Parent.Issuetype = "Story"; """{color:green}[${Parent.Issuetype}]{color} ${Parent.Summary}"""; EpicLink; """{color:#713A82}[${EpicLink.Issuetype}]{color} ${EpicLink.EpicName}""" )
Tại sao công thức bắt đầu bằng điều kiện if, nếu chúng ta chỉ cần xuất một chuỗi rồi chèn loại tác vụ và tên vào đó? Không có cách nào phổ biến để truy cập các trường nhiệm vụ sao? Có, nhưng đối với nhiệm vụ và sử thi, các trường này được đặt tên khác nhau và bạn cũng cần truy cập chúng theo cách khác nhau, đây là một tính năng của Jira.
Sự khác biệt bắt đầu ở cấp độ tìm kiếm gốc. Đối với một nhiệm vụ phụ, nhiệm vụ gốc sẽ sống trong trường Jira “Vấn đề gốc” và đối với một nhiệm vụ thông thường, sử thi sẽ là nhiệm vụ gốc, nằm trong trường “Liên kết sử thi”. Theo đó, chúng ta sẽ phải viết hai tùy chọn khác nhau để truy cập các trường này.
Đây là nơi chúng ta cần một điều kiện if. Ngôn ngữ Expr có nhiều cách khác nhau để xử lý các điều kiện. Sự lựa chọn giữa chúng là vấn đề sở thích.
Có một phương pháp "giống như excel":
if (condition1; result1; condition2; result2 … )
Hoặc một phương thức “giống mã” hơn:
if condition1 : result1 else if condition2 : result2 else result3
Trong ví dụ này, tôi đã sử dụng tùy chọn đầu tiên; bây giờ hãy xem mã của chúng tôi một cách đơn giản:
if( Parent.Issuetype = "Story"; Some kind of result 1; EpicLink; Some kind of result 2 )
Chúng ta thấy hai điều kiện rõ ràng:
Hãy cùng tìm hiểu xem họ làm gì và bắt đầu với cái đầu tiên, Parent.Issuetype=”Story”.
Trong trường hợp này, Cấp độ gốc là một biến được ánh xạ tự động tới trường “Vấn đề cấp độ gốc”. Đây là nơi, như chúng ta đã thảo luận ở trên, nhiệm vụ gốc của nhiệm vụ phụ sẽ tồn tại. Bằng cách sử dụng ký hiệu dấu chấm (.), chúng ta truy cập vào thuộc tính của cha mẹ này, đặc biệt là thuộc tính Issuetype, tương ứng với trường Jira “Loại vấn đề”. Hóa ra toàn bộ dòng Parent.Issuetype trả về cho chúng ta loại tác vụ gốc, nếu tác vụ đó tồn tại.
Ngoài ra, chúng tôi không cần phải xác định hay lập bản đồ bất cứ điều gì vì các nhà phát triển đã cố gắng hết sức cho chúng tôi. Ví dụ: đây là liên kết đến tất cả các thuộc tính (bao gồm cả trường Jira) được xác định trước bằng ngôn ngữ và tại đây bạn có thể xem danh sách tất cả các biến tiêu chuẩn, cũng có thể được truy cập một cách an toàn mà không cần cài đặt bổ sung.
Vì vậy, điều kiện đầu tiên là xem loại tác vụ gốc có phải là Story hay không. Nếu điều kiện đầu tiên không được thỏa mãn thì loại tác vụ gốc không phải là Câu chuyện hoặc hoàn toàn không tồn tại. Và điều này đưa chúng ta đến điều kiện thứ hai: EpicLink.
Trên thực tế, đây là lúc chúng tôi kiểm tra xem trường Jira “Epic Link” có được điền hay không (nghĩa là chúng tôi kiểm tra sự tồn tại của nó). Biến EpicLink cũng là biến tiêu chuẩn và không cần phải ánh xạ. Hóa ra điều kiện của chúng tôi được thỏa mãn nếu nhiệm vụ có Epic Link.
Và lựa chọn thứ ba là khi không có điều kiện nào được đáp ứng, tức là nhiệm vụ không có cha cũng không có Epic Link. Trong trường hợp này, chúng tôi không hiển thị bất cứ điều gì và để trống trường này. Việc này được thực hiện tự động vì chúng tôi sẽ không nhận được bất kỳ kết quả nào.
Chúng ta đã tìm ra các điều kiện, bây giờ chúng ta hãy chuyển sang phần kết quả. Trong cả hai trường hợp, đó là một chuỗi có văn bản và định dạng đặc biệt.
Kết quả 1 (nếu cha mẹ là Story):
"""{color:green}[${Parent.Issuetype}]{color} ${Parent.Summary}"""
Kết quả 2 (nếu có Epic Link):
"""{color:#713A82}[${EpicLink.Issuetype}]{color} ${EpicLink.EpicName}"""
Cả hai kết quả đều có cấu trúc tương tự nhau: cả hai đều bao gồm ba dấu ngoặc kép “”” ở đầu và cuối chuỗi đầu ra, đặc tả màu sắc trong khối {color: COLOR} mở và khối {color} đóng, cũng như các thao tác được thực hiện thông qua biểu tượng $. Dấu ngoặc kép cho cấu trúc biết rằng bên trong sẽ có các biến, thao tác hoặc khối định dạng (chẳng hạn như màu sắc).
Để có kết quả của điều kiện đầu tiên, chúng tôi:
Do đó, chúng ta nhận được chuỗi “[Story] Some task name.” Như bạn có thể đoán, Tóm tắt cũng là một biến tiêu chuẩn. Để làm cho sơ đồ xây dựng các chuỗi như vậy rõ ràng hơn, hãy để tôi chia sẻ hình ảnh từ tài liệu chính thức.
Theo cách tương tự, chúng tôi thu thập chuỗi cho kết quả thứ hai nhưng đặt màu thông qua mã hex. Tôi đã tìm ra rằng màu của sử thi là “#713A82” (nhân tiện, trong phần nhận xét, bạn có thể đề xuất màu chính xác hơn cho Epic). Đừng quên các trường (thuộc tính) thay đổi đối với Epic. Thay vì “Tóm tắt”, hãy sử dụng “EpicName”, thay vì “Parent”, hãy sử dụng “EpicLink”.
Kết quả là, sơ đồ công thức của chúng tôi có thể được biểu diễn dưới dạng bảng điều kiện.
Điều kiện: Tác vụ gốc tồn tại và loại của nó là Câu chuyện.
Kết quả: Dòng có loại tác vụ gốc màu xanh lá cây và tên của nó.
Điều kiện: Trường Epic Link đã được điền.
Kết quả: Phù hợp với màu sắc hoành tráng của loại và tên của nó.
Theo mặc định, tùy chọn hiển thị “Chung” được chọn trong trường và nếu bạn không thay đổi tùy chọn này, kết quả sẽ trông giống như văn bản thuần túy mà không thay đổi màu sắc và xác định các khối. Nếu bạn thay đổi định dạng hiển thị thành “Đánh dấu Wiki”, văn bản sẽ được chuyển đổi.
Bây giờ, hãy làm quen với các biến không liên quan đến trường Jira - biến cục bộ.
Từ ví dụ trước, bạn đã biết rằng chúng ta đang làm việc với các nhiệm vụ thuộc loại Câu chuyện, có các nhiệm vụ phụ. Điều này dẫn đến một trường hợp đặc biệt với ước tính. Để có được điểm Câu chuyện, chúng tôi tóm tắt điểm của các nhiệm vụ phụ trong đó, được ước tính bằng điểm Câu chuyện trừu tượng.
Cách tiếp cận này không bình thường nhưng nó hiệu quả với chúng tôi. Vì vậy, khi Câu chuyện không có ước tính nhưng nhiệm vụ phụ thì có thì không có vấn đề gì, nhưng khi cả Câu chuyện và nhiệm vụ phụ đều có ước tính thì tùy chọn tiêu chuẩn từ Cấu trúc, “Σ Điểm câu chuyện”, hoạt động không chính xác.
Điều này là do ước tính của Story được thêm vào tổng các nhiệm vụ phụ. Kết quả là số tiền hiển thị sai trong Story. Chúng tôi muốn tránh điều này và thêm dấu hiệu về sự không nhất quán với ước tính đã thiết lập trong Câu chuyện và tổng số nhiệm vụ phụ.
Chúng tôi cần một số điều kiện vì tất cả phụ thuộc vào việc ước tính có được đặt trong Story hay không.
Vậy điều kiện là:
Khi Story không có ước tính , chúng tôi hiển thị tổng ước tính của nhiệm vụ phụ bằng màu cam để cho biết giá trị này chưa được đặt trong Story
Nếu Story có ước tính thì hãy kiểm tra xem nó có tương ứng với tổng ước tính của nhiệm vụ phụ hay không:
Cách diễn đạt các điều kiện này có thể gây nhầm lẫn, vì vậy hãy diễn đạt chúng dưới dạng sơ đồ.
Đặc điểm cấu trúc được sử dụng
Một ví dụ mã
with isEstimated = storypoints != undefined: with childrenSum = sum#children{storypoints}: with isStory = issueType = "Story": with isErr = isStory AND childrenSum != storypoints: with color = if isStory : if isEstimated : if isErr : "red" else "green" else "orange": if isEstimated : """{color:$color}$storypoints{color} ${if isErr :""" ($childrenSum)"""}""" else """{color:$color}$childrenSum{color}"""
Trước khi đi sâu vào mã, hãy chuyển đổi sơ đồ của chúng ta sang một cách “giống mã” hơn để hiểu những biến chúng ta cần.
Từ sơ đồ này, chúng tôi thấy rằng chúng tôi sẽ cần:
Các biến điều kiện:
Một biến màu văn bản - màu
Hai biến ước lượng:
Hơn nữa, biến màu cũng phụ thuộc vào một số điều kiện, chẳng hạn như tính khả dụng của ước tính và loại nhiệm vụ trong dòng (xem sơ đồ bên dưới).
Vì vậy, để xác định màu, chúng ta sẽ cần một biến điều kiện khác, isStory, cho biết loại nhiệm vụ có phải là Story hay không.
Biến sp (điểm cốt truyện) sẽ là biến tiêu chuẩn, nghĩa là nó sẽ tự động ánh xạ tới trường Jira thích hợp. Chúng ta nên tự xác định phần còn lại của các biến và chúng sẽ mang tính cục bộ đối với chúng ta.
Bây giờ hãy thử triển khai các lược đồ trong mã. Đầu tiên, hãy xác định tất cả các biến.
with isEstimated = storypoints != undefined: with childrenSum = sum#children{storypoints}: with isStory = issueType = "Story": with isErr = isStory AND childrenSum != storypoints:
Các dòng được thống nhất theo cùng một sơ đồ cú pháp: từ khóa with, tên biến và ký hiệu dấu hai chấm “:” ở cuối dòng.
Từ khóa with được sử dụng để biểu thị các biến cục bộ (và các hàm tùy chỉnh, nhưng sẽ có nhiều thông tin hơn về điều đó trong một ví dụ riêng). Nó cho biết công thức tiếp theo là một biến không cần phải ánh xạ. Dấu hai chấm “:” đánh dấu sự kết thúc của định nghĩa biến.
Do đó, chúng tôi tạo biến isEstimated (xin nhắc lại, trường hợp đó không quan trọng). Chúng tôi sẽ lưu trữ 1 hoặc 0 trong đó, tùy thuộc vào việc trường điểm câu chuyện có được điền hay không. Biến storypoints được ánh xạ tự động vì trước đây chúng tôi chưa tạo biến cục bộ có cùng tên (ví dụ: với storypoints = … :).
Biến không xác định biểu thị sự không tồn tại của một cái gì đó (dưới dạng null, NaN và tương tự trong các ngôn ngữ khác). Do đó, biểu thức storypoints != undefined có thể được đọc dưới dạng câu hỏi: “Trường storypoints đã được điền chưa?”.
Tiếp theo, chúng ta nên xác định tổng điểm câu chuyện của tất cả các nhiệm vụ con. Để làm điều này, chúng ta tạo một biến cục bộ: ChildrenSum.
with childrenSum = sum#children{storypoints}:
Tổng này được tính thông qua hàm tổng hợp. (Bạn có thể đọc về các hàm như thế này trong tài liệu chính thức .) Tóm lại, Cấu trúc có thể thực hiện nhiều thao tác khác nhau với các tác vụ, có tính đến hệ thống phân cấp của chế độ xem hiện tại.
Chúng tôi sử dụng hàm tổng và ngoài nó, bằng cách sử dụng ký hiệu “#”, chúng tôi chuyển các phần tử làm rõ, hàm này chỉ giới hạn việc tính tổng cho bất kỳ tác vụ con nào của dòng hiện tại. Trong dấu ngoặc nhọn, chúng tôi cho biết trường nào chúng tôi muốn tóm tắt - chúng tôi cần ước tính theo điểm câu chuyện.
Biến cục bộ tiếp theo, isStory, lưu trữ một điều kiện: loại tác vụ trong dòng hiện tại có phải là Story hay không.
with isStory = issueType = "Story":
Chúng ta chuyển sang biến issueType, quen thuộc trong ví dụ trước, tức là loại tác vụ tự ánh xạ tới trường mong muốn. Chúng tôi làm điều này vì đây là một biến tiêu chuẩn và trước đây chúng tôi chưa xác định nó thông qua.
Bây giờ, hãy xác định biến isErr - nó báo hiệu sự khác biệt giữa tổng nhiệm vụ con và ước tính Story.
with isErr = isStory AND childrenSum != storypoints:
Ở đây chúng tôi đang sử dụng các biến cục bộ isStory và ChildrenSum mà chúng tôi đã tạo trước đó. Để báo hiệu lỗi, chúng ta cần đáp ứng đồng thời hai điều kiện: loại vấn đề là Story (isStory) và (AND) tổng số điểm con (childrenSum) không bằng (!=) với ước tính đã đặt trong tác vụ (storypoints) ). Giống như trong JQL, chúng ta có thể sử dụng các từ liên kết khi tạo điều kiện, như AND hoặc OR.
Lưu ý rằng đối với mỗi biến cục bộ đều có ký hiệu “:” ở cuối dòng. Nó phải ở cuối, sau tất cả các thao tác xác định biến. Ví dụ: nếu chúng ta cần chia định nghĩa của một biến thành nhiều dòng thì dấu hai chấm “:” chỉ được đặt sau thao tác cuối cùng. Như trong ví dụ với biến màu - màu của văn bản.
with color = if isStory : if isEstimated : if isErr : "red" else "green" else "orange":
Ở đây chúng ta thấy rất nhiều dấu “:”, nhưng chúng đóng những vai trò khác nhau. Dấu hai chấm sau if isStory là kết quả của điều kiện isStory. Hãy nhớ lại cấu trúc: if condition : result. Hãy trình bày cấu trúc này ở dạng phức tạp hơn, định nghĩa một biến.
with variable = (if condition: (if condition2 : result2 else result3) ):
Nó chỉ ra rằng nếu điều kiện2 : result2 thì kết quả3 khác là kết quả của điều kiện đầu tiên và ở cuối có dấu hai chấm “:”, hoàn thành định nghĩa của biến.
Thoạt nhìn, định nghĩa về màu sắc có vẻ phức tạp, mặc dù trên thực tế, chúng tôi đã mô tả ở đây sơ đồ định nghĩa màu sắc được trình bày ở phần đầu của ví dụ. Chỉ là do kết quả của điều kiện đầu tiên, một điều kiện khác bắt đầu — một điều kiện lồng nhau và một điều kiện khác trong đó.
Nhưng kết quả cuối cùng hơi khác so với sơ đồ được trình bày trước đó.
if isEstimated : """{color:$color}$storypoints{color} ${if isErr :""" ($childrenSum)"""}""" else """{color:$color}$childrenSum{color}"""
Chúng ta không phải viết “{color}$sp'' hai lần trong mã như trong sơ đồ; chúng ta sẽ thông minh hơn về mọi thứ. Trong nhánh, nếu tác vụ có ước tính, chúng tôi sẽ luôn hiển thị {color: $color}$storypoints{color} (nghĩa là chỉ ước tính về điểm câu chuyện với màu cần thiết) và nếu có lỗi thì Sau một khoảng trắng, chúng ta sẽ bổ sung vào dòng tổng ước tính của nhiệm vụ phụ: ($childrenSum).
Nếu không có lỗi thì sẽ không được thêm vào. Tôi cũng lưu ý bạn rằng không có ký hiệu “:” vì chúng ta không xác định một biến mà hiển thị kết quả cuối cùng thông qua một điều kiện.
Chúng ta có thể đánh giá công việc của mình trong hình ảnh bên dưới trong trường “∑SP (mod)”. Ảnh chụp màn hình hiển thị cụ thể hai trường bổ sung:
Với sự trợ giúp của những ví dụ này, chúng tôi đã phân tích các tính năng chính của ngôn ngữ cấu trúc sẽ giúp bạn giải quyết hầu hết các vấn đề. Bây giờ chúng ta hãy xem xét hai tính năng hữu ích hơn, các hàm và mảng của chúng ta. Chúng ta sẽ xem cách tạo chức năng tùy chỉnh của riêng mình.
Đôi khi có nhiều nhiệm vụ trong một sprint và chúng ta có thể bỏ lỡ những thay đổi nhỏ trong đó. Ví dụ: chúng ta có thể bỏ lỡ một nhiệm vụ phụ mới hoặc thực tế là một trong các câu chuyện đã chuyển sang giai đoạn tiếp theo. Sẽ thật tuyệt nếu có một công cụ thông báo cho chúng ta về những thay đổi quan trọng mới nhất trong nhiệm vụ.
Chúng tôi quan tâm đến ba loại thay đổi trạng thái nhiệm vụ đã xảy ra kể từ ngày hôm qua: chúng tôi bắt đầu thực hiện nhiệm vụ, một nhiệm vụ mới xuất hiện, nhiệm vụ đã đóng. Ngoài ra, sẽ rất hữu ích khi thấy rằng tác vụ được kết thúc với độ phân giải “Sẽ không thực hiện”.
Để thực hiện việc này, chúng tôi sẽ tạo một trường có chuỗi biểu tượng cảm xúc chịu trách nhiệm về những thay đổi mới nhất. Ví dụ: nếu một nhiệm vụ được tạo vào ngày hôm qua và chúng tôi đã bắt đầu thực hiện nó thì nhiệm vụ đó sẽ được đánh dấu bằng hai biểu tượng cảm xúc: “Đang thực hiện” và “Nhiệm vụ mới”.
Tại sao chúng ta cần một trường tùy chỉnh như vậy, nếu một số trường bổ sung có thể được hiển thị, chẳng hạn như ngày chuyển sang trạng thái “Đang xử lý” hoặc trường “Giải pháp” riêng biệt? Câu trả lời rất đơn giản - mọi người cảm nhận biểu tượng cảm xúc dễ dàng và nhanh hơn văn bản, vốn nằm trong các lĩnh vực khác nhau và cần được phân tích. Công thức sẽ thu thập mọi thứ ở một nơi và phân tích nó cho chúng ta, điều này sẽ giúp chúng ta tiết kiệm công sức và thời gian cho những việc hữu ích hơn.
Hãy xác định xem biểu tượng cảm xúc khác nhau sẽ chịu trách nhiệm gì:
Đặc điểm cấu trúc được sử dụng
Một ví dụ mã
if defined(issueType): with now = now(): with daysScope = 1.3: with workDaysBetween(today, from)= ( with weekends = (Weeknum(today) - Weeknum(from)) * 2: HOURS_BETWEEN(from;today)/24 - weekends ): with daysAfterCreated = workDaysBetween(now,created): with daysAfterStart = workDaysBetween(now,latestTransitionToProgress): with daysAfterDone = workDaysBetween(now, resolutionDate): with isWontDo = resolution = "Won't Do": with isRecentCreated = daysAfterCreated >= 0 and daysAfterCreated <= daysScope and not(resolution): with isRecentWork = daysAfterStart >= 0 and daysAfterStart <= daysScope : with isRecentDone = daysAfterDone >= 0 and daysAfterDone <= daysScope : concat( if isRecentCreated : "*️⃣", if isRecentWork : "🚀", if isRecentDone : "✅", if isWontDo : "❌")
Phân tích giải pháp
Để bắt đầu, hãy nghĩ về các biến toàn cục mà chúng ta cần để xác định các sự kiện mà chúng ta quan tâm. Chúng ta cần biết, nếu kể từ hôm qua:
Việc sử dụng các biến hiện có cùng với các biến ánh xạ mới sẽ giúp chúng tôi kiểm tra tất cả các điều kiện này.
Hãy chuyển sang mã. Dòng đầu tiên bắt đầu bằng điều kiện kiểm tra xem loại nhiệm vụ có tồn tại hay không.
if defined(issueType):
Điều này được thực hiện thông qua hàm được xác định sẵn, hàm này kiểm tra sự tồn tại của trường được chỉ định. Việc kiểm tra được thực hiện để tối ưu hóa việc tính toán công thức.
Chúng tôi sẽ không tải Cấu trúc với các phép tính vô ích nếu dòng không phải là một nhiệm vụ. Hóa ra tất cả mã sau if là kết quả, ý tôi là, phần thứ hai của cấu trúc if (điều kiện : result). Và nếu điều kiện không được đáp ứng thì mã cũng sẽ không hoạt động.
Dòng tiếp theo với now = now(): cũng cần thiết để tối ưu hóa các phép tính. Hơn nữa trong mã, chúng ta sẽ phải so sánh các ngày khác nhau với ngày hiện tại nhiều lần. Để không thực hiện cùng một phép tính nhiều lần, chúng tôi sẽ tính ngày này một lần và biến nó thành một biến cục bộ ngay bây giờ.
Sẽ thật tuyệt nếu giữ riêng “ngày hôm qua” của chúng ta. Theo kinh nghiệm, “ngày hôm qua” thuận tiện đã biến thành 1,3 ngày. Hãy biến điều này thành một biến: với DaysScope = 1.3:.
Bây giờ chúng ta cần tính số ngày giữa hai ngày nhiều lần. Ví dụ: giữa ngày hiện tại và ngày bắt đầu công việc. Tất nhiên, có một hàm DAYS_BETWEEN tích hợp sẵn, có vẻ phù hợp với chúng tôi. Tuy nhiên, nếu nhiệm vụ chẳng hạn được tạo vào thứ Sáu, thì vào thứ Hai, chúng tôi sẽ không thấy thông báo về nhiệm vụ mới, vì trên thực tế đã hơn 1,3 ngày trôi qua. Ngoài ra, hàm DAYS_BETWEEN chỉ đếm tổng số ngày (tức là 0,5 ngày sẽ chuyển thành 0 ngày), điều này cũng không phù hợp với chúng ta.
Chúng tôi đã đưa ra một yêu cầu — chúng tôi cần tính toán chính xác số ngày làm việc giữa những ngày này; và một chức năng tùy chỉnh sẽ giúp chúng ta điều này.
Cú pháp định nghĩa của nó rất giống với cú pháp định nghĩa một biến cục bộ. Sự khác biệt duy nhất và bổ sung duy nhất là việc liệt kê các đối số tùy chọn trong dấu ngoặc đầu tiên. Dấu ngoặc thứ hai chứa các thao tác sẽ được thực hiện khi hàm của chúng ta được gọi. Định nghĩa này của hàm không phải là định nghĩa khả thi duy nhất, nhưng chúng ta sẽ sử dụng định nghĩa này (bạn có thể tìm thấy những định nghĩa khác trong tài liệu chính thức ).
with workDaysBetween(today, from)= ( with weekends = (Weeknum(today) - Weeknum(from)) * 2: HOURS_BETWEEN(from;today)/24 - weekends ):
Hàm WorkDaysBetween tùy chỉnh của chúng tôi sẽ tính toán số ngày làm việc giữa ngày hôm nay và ngày bắt đầu, được truyền dưới dạng đối số. Logic của hàm rất đơn giản: chúng ta đếm số ngày nghỉ và trừ chúng khỏi tổng số ngày giữa các ngày.
Để tính số ngày nghỉ, chúng ta cần tìm hiểu từ hôm nay đến nay đã trôi qua bao nhiêu tuần. Để làm điều này, chúng tôi tính toán sự khác biệt giữa các con số của mỗi tuần. Chúng ta sẽ lấy số này từ hàm Weeknum, hàm này cung cấp cho chúng ta số tuần từ đầu năm. Nhân số chênh lệch này với hai, chúng ta sẽ có được số ngày nghỉ đã trôi qua.
Tiếp theo, hàm HOURS_BETWEEN đếm số giờ giữa các ngày của chúng ta. Chúng tôi chia kết quả cho 24 để có số ngày và trừ đi số ngày nghỉ trong số này, để chúng tôi có được số ngày làm việc giữa các ngày.
Sử dụng hàm mới của chúng ta, hãy xác định một loạt các biến phụ trợ. Lưu ý rằng một số ngày tháng trong định nghĩa là các biến toàn cục mà chúng ta đã nói đến ở phần đầu của ví dụ.
with daysAfterCreated = workDaysBetween(now,created): with daysAfterStart = workDaysBetween(now,latestTransitionToProgress): with daysAfterDone = workDaysBetween(now, resolutionDate):
Để làm cho mã thuận tiện cho việc đọc, hãy xác định các biến lưu trữ kết quả của các điều kiện.
with isWontDo = resolution = "Won't Do": with isRecentCreated = daysAfterCreated >= 0 and daysAfterCreated <= daysScope and not(resolution): with isRecentWork = daysAfterStart >= 0 and daysAfterStart <= daysScope : with isRecentDone = daysAfterDone >= 0 and daysAfterDone <= daysScope :
Đối với biến isRecentCreated, tôi đã thêm một điều kiện tùy chọn chứ không phải (độ phân giải), điều này giúp tôi đơn giản hóa dòng tương lai, vì nếu tác vụ đã được đóng thì tôi không quan tâm đến thông tin về lần tạo gần đây của nó.
Kết quả cuối cùng được xây dựng thông qua hàm concat, nối các dòng.
concat( if isRecentCreated : "*️⃣", if isRecentWork : "🚀", if isRecentDone : "✅", if isWontDo : "❌")
Hóa ra biểu tượng cảm xúc sẽ chỉ nằm trong dòng khi biến trong điều kiện bằng 1. Do đó, dòng của chúng ta có thể đồng thời hiển thị các thay đổi độc lập đối với tác vụ.
Chúng ta đã đề cập đến chủ đề tính ngày làm việc không có ngày nghỉ. Có một vấn đề khác liên quan đến vấn đề này mà chúng ta sẽ phân tích trong ví dụ cuối cùng, đồng thời làm quen với mảng.
Đôi khi chúng ta muốn biết một tác vụ đã chạy được bao lâu, không kể ngày nghỉ. Điều này là cần thiết, ví dụ, để phân tích phiên bản đã phát hành. Để hiểu tại sao chúng ta cần ngày nghỉ. Ngoại trừ một chuyến chạy từ thứ Hai đến thứ Năm, và chuyến còn lại từ thứ Sáu đến thứ Hai. Trong tình huống như vậy, chúng ta không thể khẳng định rằng các nhiệm vụ là tương đương nhau, mặc dù sự khác biệt về số ngày theo lịch cho chúng ta biết điều ngược lại.
Thật không may, Cấu trúc “có sẵn” không biết cách bỏ qua ngày nghỉ và trường có tùy chọn “Thời gian ở trạng thái…” tạo ra kết quả bất kể cài đặt Jira - ngay cả khi Thứ Bảy và Chủ nhật được chỉ định là ngày nghỉ.
Do đó, mục tiêu của chúng tôi là tính toán chính xác số ngày làm việc, bỏ qua số ngày nghỉ và tính đến tác động của việc chuyển đổi trạng thái vào thời gian này.
Và trạng thái có liên quan gì đến nó? Hãy để tôi trả lời. Giả sử chúng ta tính toán rằng từ ngày 10 tháng 3 đến ngày 20 tháng 3, nhiệm vụ đã được thực hiện trong ba ngày. Nhưng trong 3 ngày này, nó bị tạm dừng một ngày và xem xét trong một ngày rưỡi. Hóa ra nhiệm vụ chỉ được thực hiện trong nửa ngày.
Giải pháp từ ví dụ trước không phù hợp với chúng tôi vì vấn đề chuyển đổi giữa các trạng thái, vì hàm tùy chỉnh workDaysBetween chỉ tính đến thời gian giữa hai ngày đã chọn.
Vấn đề này có thể được giải quyết theo những cách khác nhau. Phương pháp trong ví dụ này đắt nhất về mặt hiệu suất nhưng lại chính xác nhất về mặt đếm ngày nghỉ và trạng thái. Lưu ý rằng việc triển khai nó chỉ hoạt động trong phiên bản Cấu trúc cũ hơn 7.4 (tháng 12 năm 2021).
Vì vậy, ý tưởng đằng sau công thức như sau:
Như vậy, chúng ta sẽ có được thời gian chính xác để thực hiện nhiệm vụ, bỏ qua những ngày nghỉ và chuyển đổi giữa các trạng thái bổ sung.
Đặc điểm cấu trúc được sử dụng
Một ví dụ mã
if defined(issueType) : if status != "Open" : with finishDate = if toQA != Undefined : toQA else if toDone != Undefined : toDone else now(): with startDate = DEFAULT(toProgress, toDone): with statusWeekendsCount(dates, status) = ( dates.filter(x -> weekday(x) > 5 and historical_value(this,"status",x)=status).size() ): with overallDays = round(hours_between(startDate,finishDate)/24): with sequenceArray = SEQUENCE(0,overallDays): with datesArray = sequenceArray.map(DATE_ADD(startDate,$,"day")): with progressWeekends = statusWeekendsCount(datesArray, "in Progress"): with progressDays = (timeInProgress/86400000 - progressWeekends).round(1): with color = if( progressDays = 0 ; "gray" ; progressDays > 0 and progressDays <= 2.5; "green" ; progressDays > 2.5 and progressDays <= 4; "orange" ; progressDays > 4; "red" ): """{color:$color}$progressDays d{color}"""
Phân tích giải pháp
Trước khi chuyển thuật toán của chúng tôi sang mã, hãy tạo điều kiện thuận lợi cho việc tính toán Cấu trúc.
if defined(issueType) : if status != "Open" :
Nếu dòng này không phải là một nhiệm vụ hoặc trạng thái của nó là “Mở”, thì chúng tôi sẽ bỏ qua những dòng đó. Chúng tôi chỉ quan tâm đến những nhiệm vụ đã được đưa ra để làm việc.
Để tính số ngày giữa các ngày, trước tiên chúng ta phải xác định những ngày sau: finishDate và startDate.
with finishDate = if toQA != Undefined : toQA else if toDone != Undefined : toDone else now(): with startDate = DEFAULT(toProgress, toDone):
Chúng tôi giả định rằng ngày hoàn thành nhiệm vụ (finishDate) là:
Ngày bắt đầu công việcNgày bắt đầu được xác định theo ngày chuyển sang trạng thái “Đang tiến hành”. Có những trường hợp tác vụ được kết thúc mà không chuyển sang giai đoạn đang làm việc. Trong những trường hợp như vậy, chúng tôi coi ngày kết thúc là ngày bắt đầu, do đó, kết quả là 0 ngày.
Như bạn có thể đoán toQA, toDone và toProgress là các biến cần được ánh xạ tới các trạng thái thích hợp như trong ví dụ đầu tiên và trước đó.
Chúng ta cũng thấy hàm DEFAULT(toProgress, toDone) mới. Nó kiểm tra xem toProgress có giá trị hay không và nếu không, nó sẽ sử dụng giá trị của biến toDone.
Tiếp theo là định nghĩa về hàm tùy chỉnh statusWeekendsCount nhưng chúng ta sẽ quay lại hàm này sau vì nó liên quan chặt chẽ đến danh sách ngày tháng. Tốt hơn hết là chúng ta nên đi thẳng vào định nghĩa của danh sách này để sau này chúng ta có thể hiểu cách áp dụng hàm của mình cho nó.
Chúng tôi muốn nhận danh sách các ngày ở dạng sau: [startDate (giả sử là 11.03), 12.03, 13.03, 14.03 … finishDate]. Không có chức năng đơn giản nào có thể thực hiện tất cả công việc cho chúng ta trong Cấu trúc. Vì vậy, hãy dùng đến một mẹo:
Bây giờ, hãy xem cách chúng ta có thể triển khai nó trong mã. Chúng ta sẽ làm việc với mảng.
with overallDays = round(hours_between(startDate,finishDate)/24): with sequenceArray = SEQUENCE(0,overallDays): with datesArray = sequenceArray.map(DATE_ADD(startDate,$,"day")):
Chúng tôi đếm xem công việc của một nhiệm vụ sẽ mất bao nhiêu ngày. Như trong ví dụ trước, thông qua phép chia cho 24 và hàm Hours_between(startDate,finishDate). Kết quả được ghi trong biến tổng thểDays.
Chúng ta tạo một mảng dãy số dưới dạng biến SequArray. Mảng này được xây dựng thông qua hàm SEQUENCE(0,overallDays), hàm này chỉ đơn giản tạo một mảng có kích thước mong muốn với một chuỗi từ 0 đến tổng sốNgày.
Tiếp theo là phép thuật. Một trong những hàm mảng là bản đồ. Nó áp dụng thao tác đã chỉ định cho từng phần tử của mảng.
Nhiệm vụ của chúng ta là thêm ngày bắt đầu vào mỗi số (tức là số trong ngày). Hàm DATE_ADD có thể thực hiện việc này, nó thêm một số ngày, tháng hoặc năm nhất định vào ngày đã chỉ định.
Biết được điều này, hãy giải mã chuỗi:
with datesArray = sequenceArray.map(DATE_ADD(startDate, $,"day"))
Đối với mỗi phần tử trong SequenceArray, hàm .map() áp dụng DATE_ADD(startDate, $, “day”).
Hãy xem những gì được truyền trong các đối số cho DATE_ADD. Điều đầu tiên là ngày bắt đầu, ngày mà số mong muốn sẽ được thêm vào. Con số này được chỉ định bởi đối số thứ hai, nhưng chúng ta thấy $.
Ký hiệu $ biểu thị một phần tử mảng. Cấu trúc hiểu rằng hàm DATE_ADD được áp dụng cho một mảng và do đó thay vì $ sẽ có phần tử mảng mong muốn (tức là 0, 1, 2…).
Đối số cuối cùng “ngày” là dấu hiệu cho thấy chúng ta thêm ngày vì hàm có thể thêm ngày, tháng và năm, tùy thuộc vào nội dung chúng ta chỉ định.
Do đó, biến dateArray sẽ lưu trữ một mảng ngày từ khi bắt đầu công việc cho đến khi hoàn thành.
Hãy quay lại chức năng tùy chỉnh mà chúng ta đã bỏ lỡ. Nó sẽ lọc ra những ngày thừa và tính toán phần còn lại. Chúng tôi đã mô tả thuật toán này ngay từ đầu ví dụ, trước khi phân tích mã, cụ thể là trong đoạn 3 và 4 về việc lọc ngày nghỉ và trạng thái.
with statusWeekendsCount(dates, status) = ( dates.filter(x -> weekday(x) > 5 and historical_value(this,"status",x)=status).size() ):
Chúng ta sẽ chuyển hai đối số cho hàm tùy chỉnh: một mảng ngày tháng, hãy gọi nó là ngày tháng và trạng thái bắt buộc — trạng thái. Chúng tôi áp dụng hàm .filter() cho mảng ngày được chuyển, chỉ giữ lại những bản ghi trong mảng đã vượt qua điều kiện lọc. Trong trường hợp của chúng tôi, có hai trong số chúng và chúng được kết hợp thông qua và. Sau bộ lọc, chúng ta thấy .size(), nó trả về kích thước của mảng sau khi tất cả các thao tác trên nó được thực hiện.
Nếu đơn giản hóa biểu thức, chúng ta sẽ nhận được kết quả như sau: array.filter(condition1 và condition2).size(). Như vậy, kết quả là chúng tôi đã có được số ngày nghỉ phù hợp với mình, tức là những ngày nghỉ đạt điều kiện.
Chúng ta hãy xem xét kỹ hơn về cả hai điều kiện:
x -> weekday(x) > 5 and historical_value(this,"status",x)=status
Biểu thức x -> chỉ là một phần của cú pháp bộ lọc, cho biết rằng chúng ta sẽ gọi phần tử của mảng x . Do đó, x xuất hiện trong mỗi điều kiện (tương tự như với $). Hóa ra x là mỗi ngày trong mảng ngày được chuyển.
Điều kiện đầu tiên, ngày trong tuần(x) > 5, yêu cầu ngày trong tuần của ngày x (nghĩa là mỗi phần tử) phải lớn hơn 5 — đó là Thứ Bảy (6) hoặc Chủ Nhật (7).
Điều kiện thứ hai sử dụng giá trị lịch sử.
historical_value(this,"status",x) = status
Đó là tính năng của Structure phiên bản 7.4.
Hàm truy cập vào lịch sử của tác vụ và tìm kiếm một ngày cụ thể trong trường được chỉ định. Trong trường hợp này, chúng tôi đang tìm kiếm ngày x trong trường “trạng thái”. Biến này chỉ là một phần của cú pháp hàm, nó được ánh xạ tự động và thể hiện tác vụ hiện tại trong dòng.
Do đó, trong điều kiện, chúng ta so sánh đối số trạng thái được chuyển và trường “trạng thái”, được hàm history_value trả về cho mỗi ngày x trong mảng. Nếu chúng khớp nhau thì mục đó vẫn còn trong danh sách.
Điểm cuối cùng là việc sử dụng chức năng của chúng tôi để đếm số ngày ở trạng thái mong muốn:
with progressWeekends = statusWeekendsCount(datesArray, "in Progress"): with progressDays = (timeInProgress/86400000 - progressWeekends).round(1):
Trước tiên, hãy tìm hiểu xem có bao nhiêu ngày nghỉ với trạng thái “đang tiến hành” trong dateArray. Nghĩa là, chúng tôi chuyển danh sách ngày và trạng thái mong muốn của mình sang hàm tùy chỉnh statusWeekendsCount. Hàm này sẽ loại bỏ tất cả các ngày trong tuần và tất cả các ngày nghỉ mà trạng thái của nhiệm vụ khác với trạng thái “Đang tiến hành” và trả về số ngày còn lại trong danh sách.
Sau đó, chúng tôi trừ số tiền này khỏi biến timeInProgress mà chúng tôi ánh xạ thông qua tùy chọn “Thời gian ở trạng thái…”.
Số 86400000 là số chia sẽ biến mili giây thành ngày. Cần có hàm .round(1) để làm tròn kết quả đến phần mười, ví dụ: thành “4.1”, nếu không, bạn có thể nhận được loại mục nhập này: “4.0999999…”.
Để chỉ ra độ dài của nhiệm vụ, chúng tôi giới thiệu biến màu. Chúng tôi sẽ thay đổi nó tùy thuộc vào số ngày dành cho nhiệm vụ.
with color = if( progressDays = 0 ; "gray" ; progressDays > 0 and progressDays <= 2.5; "green" ; progressDays > 2.5 and progressDays <= 4; "orange" ; progressDays > 4; "red" ):
Và dòng cuối cùng là kết quả số ngày tính toán:
"""{color:$color}$progressDays d{color}"""
Kết quả của chúng ta sẽ giống như hình bên dưới.
Nhân tiện, trong cùng một công thức, bạn có thể hiển thị thời gian của bất kỳ trạng thái nào. Ví dụ: nếu chúng ta chuyển trạng thái “Tạm dừng” cho hàm tùy chỉnh của mình và ánh xạ biến timeInProgress thông qua “Thời gian trong… — Tạm dừng”, thì chúng ta sẽ tính toán thời gian chính xác khi tạm dừng.
Bạn có thể kết hợp các trạng thái và tạo một mục như “wip: 3.2d | rev:12d”, tức là tính thời gian làm việc và thời gian xem xét. Bạn chỉ bị giới hạn bởi trí tưởng tượng và quy trình làm việc của mình.
Chúng tôi đã trình bày đầy đủ các tính năng của ngôn ngữ công thức này sẽ giúp bạn làm điều gì đó tương tự hoặc viết điều gì đó hoàn toàn mới và thú vị để phân tích các nhiệm vụ của Jira.
Tôi hy vọng bài viết đã giúp bạn tìm ra các công thức, hoặc ít nhất khiến bạn quan tâm đến chủ đề này. Tôi không khẳng định rằng tôi có “mã và thuật toán tốt nhất”, vì vậy nếu bạn có ý tưởng về cách cải thiện các ví dụ, tôi rất vui nếu bạn chia sẻ chúng!
Tất nhiên, bạn cần hiểu rằng không ai có thể cho bạn biết về công thức tốt hơn các nhà phát triển ALM Works. Vì vậy, tôi đang đính kèm các liên kết tới tài liệu và hội thảo trên web của họ. Và nếu bạn bắt đầu làm việc với các trường tùy chỉnh, hãy kiểm tra chúng thường xuyên để xem bạn có thể sử dụng những tính năng nào khác.